package world.hezi.utils;

import org.opencv.calib3d.Calib3d;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfDMatch;
import org.opencv.core.MatOfKeyPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.features2d.Features2d;
import org.opencv.features2d.FlannBasedMatcher;
import org.opencv.features2d.SIFT;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import world.hezi.entity.SIFTResponse;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * @author wangzijian
 * @since 2023/11/9 15:32
 */
public class OpenCvUtil {
    static {
        System.load("C:\\opencv_java480.dll");
    }

    public static void main(String[] args) {
        File file = new File("D:\\haha");
        int index = 0;
        for (File listFile : file.listFiles()) {
            try {
                Mat imread1 = Imgcodecs.imread(listFile.getAbsolutePath());
                Imgcodecs.imwrite("D:\\hahan\\"+UUID.randomUUID()+".jpg",imread1);
                System.out.println(++index);
            }catch (Exception e){
                e.printStackTrace();
            }
        }

    }
    public static SIFTResponse analysisSIFT(File target, File descriptorDir) {
        try {
            String absolutePath = target.getAbsolutePath();
            List<SIFTResponse> responses = new ArrayList<>();
            for (File file : descriptorDir.listFiles()) {
                String facePath = file.getAbsolutePath();
                Mat imread1 = Imgcodecs.imread(facePath, Imgcodecs.IMREAD_GRAYSCALE);
                Mat imread2 = Imgcodecs.imread(absolutePath, Imgcodecs.IMREAD_GRAYSCALE);
                SIFTResponse sift = sift(contrastEnhancement(imread1), contrastEnhancement(imread2));
                responses.add(sift);
            }
            responses.sort((o1, o2) -> Double.compare((double)o2.getGoodMatches()/(double)o2.getAllMatches(),(double)o1.getGoodMatches()/(double)o1.getAllMatches()));
            return responses.get(0);

        }catch (Exception e){
            return null;
        }
    }

    /**
     * 使用SIFT的方式对比两张照片
     * @param obj 目标图片，例如人脸图片
     * @param objInScene 目标在环境中的图片，例如包含人脸的整个身体图片
     * @return SIFTResponse
     */
    public static SIFTResponse sift(Mat obj, Mat objInScene){
        //缩放obj使其尺寸小于objInScene
        if (obj.cols() > objInScene.cols() || obj.rows() > objInScene.rows()){
            double widthD = (double)obj.width() / (double)objInScene.width();
            double heightD = (double)obj.height() / (double)objInScene.height();
            double max = Math.max(widthD, heightD) * 1.2;
            double resize = 1/max;
            Size size = new Size(obj.width()*resize, obj.height()*resize);
            Imgproc.resize(obj,obj,size);
        }
        SIFT sift = SIFT.create();
        MatOfKeyPoint kpt1 = new MatOfKeyPoint();
        MatOfKeyPoint kpt2 = new MatOfKeyPoint();
        Mat desc1 = new Mat();
        Mat desc2 = new Mat();
        sift.detectAndCompute(obj,new Mat(),kpt1,desc1);
        sift.detectAndCompute(objInScene,new Mat(),kpt2,desc2);

        FlannBasedMatcher knnMatcher = FlannBasedMatcher.create();
        List<MatOfDMatch> maches = new ArrayList<>();
        knnMatcher.knnMatch(desc1,desc2,maches,7);
        float kRatioThresh = 0.8f;
        List<MatOfDMatch> goodMatches = new ArrayList<>();
        for (MatOfDMatch mach : maches) {
            if (mach.toArray()[0].distance < kRatioThresh*mach.toArray()[1].distance){
                goodMatches.add(mach);
            }
        }

        Mat imgMatches = new Mat();
        Features2d.drawMatchesKnn(obj,kpt1,objInScene,kpt2,goodMatches,imgMatches);
        List<Point> objs = new ArrayList<>();
        List<Point> scenes = new ArrayList<>();
        for (MatOfDMatch goodMatch : goodMatches) {
            objs.add(kpt1.toArray()[goodMatch.toArray()[0].queryIdx].pt);
            scenes.add(kpt2.toArray()[goodMatch.toArray()[0].trainIdx].pt);
        }
        Point[] objsArr = new Point[objs.size()];
        Point[] scenesArr = new Point[scenes.size()];
        objs.toArray(objsArr);
        scenes.toArray(scenesArr);
        try {
            if (objs.size() >= 4){
                Mat homography = Calib3d.findHomography(new MatOfPoint2f(objsArr), new MatOfPoint2f(scenesArr), Calib3d.RANSAC,9);
                Point[] objCorners = new Point[4];

                objCorners[0] = new Point(0,0);
                objCorners[1] = new Point(obj.cols(), 0);
                objCorners[2] = new Point(obj.cols(), obj.rows());
                objCorners[3] = new Point(0, obj.rows());
                Point[] sceneCorners = new Point[4];
                for (int i = 0; i < sceneCorners.length; i++) {
                    sceneCorners[i] = new Point();
                }
                MatOfPoint2f scene = new MatOfPoint2f(sceneCorners);
                Core.perspectiveTransform(new MatOfPoint2f(objCorners), scene , homography);

                Point[] points = scene.toArray();
                Imgproc.line(imgMatches,new Point(points[0].x + obj.cols(), points[0].y),new Point(points[1].x + obj.cols(), points[1].y),new Scalar(0,255,0));
                Imgproc.line(imgMatches,new Point(points[1].x + obj.cols(), points[1].y),new Point(points[2].x + obj.cols(), points[2].y),new Scalar(0,255,0));
                Imgproc.line(imgMatches,new Point(points[2].x + obj.cols(), points[2].y),new Point(points[3].x + obj.cols(), points[3].y),new Scalar(0,255,0));
                Imgproc.line(imgMatches,new Point(points[3].x + obj.cols(), points[3].y),new Point(points[0].x + obj.cols(), points[0].y),new Scalar(0,255,0));
            }
        }catch (Exception e){
            //ignore
        }
        SIFTResponse siftResponse = new SIFTResponse();
        siftResponse.setMat(imgMatches);
        siftResponse.setGoodMatches(goodMatches.size());
        siftResponse.setAllMatches(maches.size());
        return siftResponse;
    }

    public static Mat contrastEnhancement(Mat srcImage){
        Mat re = new Mat();
        srcImage.convertTo(re,-1, 1,30);
        return re;
    }

    public void showMat(Mat mat){
        HighGui.imshow(UUID.randomUUID().toString(),mat);
        HighGui.waitKey();
    }
}
